import os
import configparser
import tkinter as tk
from tkinter import ttk, messagebox
import azure.cognitiveservices.speech as speechsdk
import threading
from datetime import datetime
import logging
import sys

# 配置日志
def setup_logging():
    log_file = os.path.join(os.path.dirname(sys.executable if getattr(sys, 'frozen', False) else __file__), 'app.log')
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s [%(levelname)s] %(message)s',
        handlers=[
            logging.FileHandler(log_file, encoding='utf-8'),
            logging.StreamHandler()
        ]
    )

class TextToSpeechApp:
    def __init__(self, root):
        self.root = root
        self.root.title('文本转语音工具')
        self.root.geometry('602x540')
        
        # 加载配置
        self.config = configparser.ConfigParser()
        self.config.read('config.ini', encoding='utf-8')
        
        # 确保输出目录存在
        self.output_dir = self.config.get('Settings', 'output_dir')
        os.makedirs(self.output_dir, exist_ok=True)
        
        # 创建语音配置
        self.speech_config = speechsdk.SpeechConfig(
            subscription=self.config.get('Azure', 'subscription_key'),
            region=self.config.get('Azure', 'region')
        )
        self.speech_config.speech_synthesis_language = 'zh-CN'
        
        # 加载保存的语音参数
        self.load_voice_settings()
        
        self.setup_ui()
    
    def load_voice_settings(self):
        """从配置文件加载语音参数"""
        if not self.config.has_section('VoiceSettings'):
            self.config.add_section('VoiceSettings')
            # 设置默认值
            self.config.set('VoiceSettings', 'voice', 'zh-CN-XiaoxiaoNeural')
            self.config.set('VoiceSettings', 'speed', '7.5')
            self.config.set('VoiceSettings', 'volume', '7.5')
            self.config.set('VoiceSettings', 'pitch', '7.5')
            self.config.set('VoiceSettings', 'format', 'wav')
            self.save_voice_settings()
    
    def save_voice_settings(self):
        """保存语音参数到配置文件"""
        if not self.config.has_section('VoiceSettings'):
            self.config.add_section('VoiceSettings')
        
        self.config.set('VoiceSettings', 'voice', self.voice_var.get() if hasattr(self, 'voice_var') else 'zh-CN-XiaoxiaoNeural')
        self.config.set('VoiceSettings', 'speed', f"{self.speed_var.get():.1f}" if hasattr(self, 'speed_var') else '7.5')
        self.config.set('VoiceSettings', 'volume', f"{self.volume_var.get():.1f}" if hasattr(self, 'volume_var') else '7.5')
        self.config.set('VoiceSettings', 'pitch', f"{self.pitch_var.get():.1f}" if hasattr(self, 'pitch_var') else '7.5')
        self.config.set('VoiceSettings', 'format', self.format_var.get() if hasattr(self, 'format_var') else 'wav')
        
        with open('config.ini', 'w', encoding='utf-8') as f:
            self.config.write(f)
    
    def setup_ui(self):
        # 文本输入区域
        input_frame = ttk.LabelFrame(self.root, text='输入文本', padding='5')
        input_frame.pack(fill='both', expand=True, padx=5, pady=5)
        
        self.text_input = tk.Text(input_frame, wrap='word', height=10)
        self.text_input.pack(fill='both', expand=True)
        
        # 语音设置区域
        settings_frame = ttk.LabelFrame(self.root, text='语音设置', padding='5')
        settings_frame.pack(fill='x', padx=5, pady=5)
        
        # 语音选择
        ttk.Label(settings_frame, text='语音：').grid(row=0, column=0, padx=5, pady=5)
        self.voice_var = tk.StringVar(value=self.config.get('VoiceSettings', 'voice'))
        self.voice_combo = ttk.Combobox(settings_frame, textvariable=self.voice_var)
        self.voice_combo['values'] = [
            'zh-CN-XiaoxiaoNeural',
            'zh-CN-YunxiNeural',
            'zh-CN-YunjianNeural',
            'zh-CN-XiaochenNeural',
            'zh-CN-XiaohanNeural',
            'zh-CN-XiaomengNeural',
            'zh-CN-XiaomoNeural',
            'zh-CN-XiaoqiuNeural',
            'zh-CN-XiaoruiNeural',
            'zh-CN-XiaoshuangNeural',
            'zh-CN-XiaoxuanNeural',
            'zh-CN-XiaoyanNeural',
            'zh-CN-XiaoyiNeural',
            'zh-CN-XiaozhenNeural'
        ]
        self.voice_combo.grid(row=0, column=1, padx=5, pady=5)
        self.voice_combo.bind('<<ComboboxSelected>>', lambda e: self.save_voice_settings())

        # 语速控制
        ttk.Label(settings_frame, text='语速：').grid(row=1, column=0, padx=5, pady=5)
        self.speed_var = tk.DoubleVar(value=float(self.config.get('VoiceSettings', 'speed')))
        speed_scale = ttk.Scale(settings_frame, from_=0, to=15, variable=self.speed_var, orient='horizontal')
        speed_scale.grid(row=1, column=1, padx=5, pady=5, sticky='ew')
        self.speed_label = ttk.Label(settings_frame, text=str(self.speed_var.get()))
        self.speed_label.grid(row=1, column=2, padx=5, pady=5)
        speed_scale.configure(command=lambda value: [self.speed_label.configure(text=f'{float(value):.1f}'), self.save_voice_settings()])

        # 音量控制
        ttk.Label(settings_frame, text='音量：').grid(row=2, column=0, padx=5, pady=5)
        self.volume_var = tk.DoubleVar(value=float(self.config.get('VoiceSettings', 'volume')))
        volume_scale = ttk.Scale(settings_frame, from_=0, to=15, variable=self.volume_var, orient='horizontal')
        volume_scale.grid(row=2, column=1, padx=5, pady=5, sticky='ew')
        self.volume_label = ttk.Label(settings_frame, text=str(self.volume_var.get()))
        self.volume_label.grid(row=2, column=2, padx=5, pady=5)
        volume_scale.configure(command=lambda value: [self.volume_label.configure(text=f'{float(value):.1f}'), self.save_voice_settings()])

        # 音调控制
        ttk.Label(settings_frame, text='音调：').grid(row=3, column=0, padx=5, pady=5)
        self.pitch_var = tk.DoubleVar(value=float(self.config.get('VoiceSettings', 'pitch')))
        pitch_scale = ttk.Scale(settings_frame, from_=0, to=15, variable=self.pitch_var, orient='horizontal')
        pitch_scale.grid(row=3, column=1, padx=5, pady=5, sticky='ew')
        self.pitch_label = ttk.Label(settings_frame, text=str(self.pitch_var.get()))
        self.pitch_label.grid(row=3, column=2, padx=5, pady=5)
        pitch_scale.configure(command=lambda value: [self.pitch_label.configure(text=f'{float(value):.1f}'), self.save_voice_settings()])

        # 格式选择
        ttk.Label(settings_frame, text='格式：').grid(row=4, column=0, padx=5, pady=5)
        format_frame = ttk.Frame(settings_frame)
        format_frame.grid(row=4, column=1, padx=5, pady=5, sticky='w')
        self.format_var = tk.StringVar(value=self.config.get('VoiceSettings', 'format'))
        ttk.Radiobutton(format_frame, text='WAV', variable=self.format_var, value='wav', command=self.save_voice_settings).pack(side='left', padx=5)
        ttk.Radiobutton(format_frame, text='MP3', variable=self.format_var, value='mp3', command=self.save_voice_settings).pack(side='left', padx=5)

        # 按钮区域
        button_frame = ttk.Frame(self.root)
        button_frame.pack(side='bottom', fill='x', padx=5, pady=5)

        # 状态显示
        self.status_var = tk.StringVar(value='')
        self.status_label = ttk.Label(button_frame, textvariable=self.status_var)
        self.status_label.pack(side='left', fill='x', expand=True, padx=(5, 10), pady=3)

        # 设置按钮样式
        style = ttk.Style()
        style.configure('Preview.TButton', padding=(20, 8))
        style.map('Preview.TButton',
            relief=[('pressed', 'sunken')])
        style.configure('Convert.TButton', padding=(20, 8))
        style.map('Convert.TButton',
            relief=[('pressed', 'sunken')])

        # 转换按钮
        self.convert_btn = ttk.Button(button_frame, text='转换为语音', command=self.convert_to_speech, style='Convert.TButton')
        self.convert_btn.pack(side='right', padx=5, pady=3)

        # 试听按钮
        self.preview_btn = ttk.Button(button_frame, text='试听', command=self.preview_speech, style='Preview.TButton')
        self.preview_btn.pack(side='right', padx=5, pady=3)

    def _create_ssml(self, text):
        # 设置语音参数
        voice_name = self.voice_var.get()
        
        # 语速：0-15映射到-50%到+50%
        rate = int((self.speed_var.get() / 7.5 - 1) * 50)
        rate_str = f"{'+' if rate > 0 else ''}{rate}%"
        
        # 音量：0-15映射到0-100
        volume = int(self.volume_var.get() / 15 * 100)
        # 确保volume值在0到100的范围内
        volume = max(0, min(100, volume))
        volume_str = f"{volume}"
        
        # 音调：0-15映射到-45%到+45%
        pitch = int((self.pitch_var.get() / 7.5 - 1) * 45)
        # 确保pitch值在-45到45的范围内
        pitch = max(-45, min(45, pitch))
        pitch_str = f"{'+' if pitch > 0 else ''}{pitch}%"
        
        # 构建SSML
        return f"""<speak version='1.0' xmlns='http://www.w3.org/2001/10/synthesis' xml:lang='zh-CN'>
            <voice name='{voice_name}'>
                <prosody rate='{rate_str}' volume='{volume_str}' pitch='{pitch_str}'>
                    {text}
                </prosody>
            </voice>
        </speak>"""

    def _synthesize_speech(self, text, output_config=None):
        try:
            # 构建SSML
            ssml = self._create_ssml(text)
            
            # 创建语音合成器
            speech_synthesizer = speechsdk.SpeechSynthesizer(
                speech_config=self.speech_config,
                audio_config=output_config or speechsdk.audio.AudioOutputConfig(use_default_speaker=True)
            )
            
            result = speech_synthesizer.speak_ssml_async(ssml).get()
            return result
        
        except Exception as e:
            raise e

    def preview_speech(self):
        text = self.text_input.get('1.0', 'end-1c').strip()
        if not text:
            messagebox.showwarning('警告', '请输入要转换的文本！')
            return
        
        # 禁用按钮
        self.preview_btn.state(['disabled'])
        self.convert_btn.state(['disabled'])
        self.status_var.set('正在播放...')
        
        def synthesis_task():
            speech_synthesizer = None
            try:
                # 构建SSML
                ssml = self._create_ssml(text)
                
                # 创建音频输出配置
                try:
                    audio_config = speechsdk.audio.AudioOutputConfig(use_default_speaker=True)
                except Exception as audio_error:
                    if 'SPXERR_AUDIO_SYS_LIBRARY_NOT_FOUND' in str(audio_error):
                        self.root.after(0, lambda: [
                            self.status_var.set('播放失败！'),
                            messagebox.showerror('错误', '无法初始化音频系统，请确保系统音频设备正常工作。\n如果问题持续存在，请尝试以下步骤：\n1. 检查系统音频设备是否正常\n2. 重新启动应用程序\n3. 重新安装应用程序')
                        ])
                    else:
                        self.root.after(0, lambda: [
                            self.status_var.set('播放失败！'),
                            messagebox.showerror('错误', f'音频系统初始化失败：{str(audio_error)}')
                        ])
                    return
                
                # 创建语音合成器
                speech_synthesizer = speechsdk.SpeechSynthesizer(
                    speech_config=self.speech_config,
                    audio_config=audio_config
                )
                
                # 设置超时时间
                speech_synthesizer.synthesis_completed.wait_for = 30000  # 30秒超时
                
                # 同步执行语音合成
                result = speech_synthesizer.speak_ssml_async(ssml).get()
                
                # 使用root.after确保在主线程中更新UI
                if result and result.reason == speechsdk.ResultReason.SynthesizingAudioCompleted:
                    self.root.after(0, lambda: self.status_var.set('播放完成'))
                else:
                    error_msg = '语音合成失败！' if not result else f'语音合成失败：{result.reason}'
                    if result and result.reason == speechsdk.ResultReason.Canceled:
                        cancellation_details = speechsdk.CancellationDetails(result)
                        if cancellation_details.reason == speechsdk.CancellationReason.Error:
                            error_msg = f'错误代码：{cancellation_details.error_code}\n错误详情：{cancellation_details.error_details}'
                    
                    self.root.after(0, lambda: [
                        self.status_var.set('播放失败！'),
                        messagebox.showerror('错误', error_msg)
                    ])
            
            except Exception as e:
                error_msg = str(e)
                if 'Connection refused' in error_msg:
                    error_msg = '无法连接到语音服务，请检查网络连接'
                elif 'Invalid subscription key' in error_msg:
                    error_msg = '无效的订阅密钥，请检查配置'
                elif 'SPXERR_AUDIO_SYS_LIBRARY_NOT_FOUND' in error_msg:
                    error_msg = '无法初始化音频系统，请确保系统音频设备正常工作'
                
                self.root.after(0, lambda: [
                    self.status_var.set('播放失败！'),
                    messagebox.showerror('错误', f'发生错误：{error_msg}')
                ])
            
            finally:
                # 确保在完成后释放语音合成器资源
                if speech_synthesizer:
                    try:
                        speech_synthesizer.stop_speaking_async().get()
                    except:
                        pass
                    finally:
                        del speech_synthesizer
                
                # 恢复按钮状态
                self.root.after(0, lambda: [
                    self.preview_btn.state(['!disabled']),
                    self.convert_btn.state(['!disabled'])
                ])
        
        # 在新线程中运行语音合成任务
        threading.Thread(target=synthesis_task, daemon=True).start()

    def convert_to_speech(self):
        text = self.text_input.get('1.0', 'end-1c').strip()
        if not text:
            messagebox.showwarning('警告', '请输入要转换的文本！')
            return
        
        # 禁用按钮
        self.convert_btn.state(['disabled'])
        self.preview_btn.state(['disabled'])
        self.status_var.set('正在转换...')
        
        def synthesis_task():
            try:
                # 创建输出配置
                output_format = self.format_var.get()
                # 生成文件名：时间戳.格式
                timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
                output_filename = f'{timestamp}.{output_format}'
                output_config = speechsdk.audio.AudioOutputConfig(
                    filename=os.path.join(self.output_dir, output_filename)
                )
                
                result = self._synthesize_speech(text, output_config)
                
                if result.reason == speechsdk.ResultReason.SynthesizingAudioCompleted:
                    self.root.after(0, lambda: [
                        self.status_var.set('转换完成！'),
                        messagebox.showinfo('成功', f'语音文件已保存到：{self.output_dir}\\{output_filename}')
                    ])
                else:
                    self.root.after(0, lambda: [
                        self.status_var.set('转换失败！'),
                        messagebox.showerror('错误', '语音合成失败！')
                    ])
            
            except Exception as e:
                self.root.after(0, lambda: [
                    self.status_var.set('转换失败！'),
                    messagebox.showerror('错误', f'发生错误：{str(e)}')
                ])
            
            finally:
                # 恢复按钮状态
                self.root.after(0, lambda: [
                    self.preview_btn.state(['!disabled']),
                    self.convert_btn.state(['!disabled'])
                ])
        
        # 在新线程中运行语音合成任务
        threading.Thread(target=synthesis_task, daemon=True).start()

def main():
    try:
        setup_logging()
        logging.info('应用程序启动')
        root = tk.Tk()
        app = TextToSpeechApp(root)
        root.mainloop()
    except Exception as e:
        logging.exception('应用程序发生异常：%s', str(e))

if __name__ == '__main__':
    main()